import 'dart:convert';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_tools/device.dart';
import 'package:flutter_tools/wxlog.dart';
import 'package:flutter_tools/string_extension.dart';
import 'user_info_proxy.dart';
import 'network_listen.dart';
import 'package:oktoast/oktoast.dart';
import 'network_package.dart' show BaseModel,ErrorModel,RequestConfig;
import 'intercept.dart';

enum Method {
  GET,
  POST,
  DELETE,
  PUT,
}

const _MethodValues = {
  Method.GET: "get",
  Method.POST: "post",
  Method.DELETE: "delete",
  Method.PUT: "put"
};

class Request {
  Dio dio;
  CancelToken _cancelToken = new CancelToken();
  Map<String, dynamic> defaultParams;
  static final Request _requestInstance = Request._internal();
  factory Request() {
    return _requestInstance;
  }

  Request._internal() {
    if (dio == null) {
      defaultParams = {
                       '_did':Device.idfa, // 设备标识符
                       '_vn':Device.appVersion,// 客户端版本号
                       '_aid': Device.isIOS ? '1' : '2',// 应用号 iOS 1，安卓 2
                       'X-JWT': UserInfoProxy.getInstance().getAccessToken(),// user token
                       '_ua':'userAgent' //userAgent
                       };
      BaseOptions options = BaseOptions(
        baseUrl: '',
        contentType: Headers.jsonContentType,
        responseType: ResponseType.plain,
        connectTimeout: 10*1000,
        receiveTimeout: 10*1000,
        headers: defaultParams
      );
      dio = Dio(options);

      dio.interceptors.add(TokenInterceptor());
      dio.interceptors.add(LoggingInterceptor());
//      if (!kReleaseMode && Application.proxy.notEmpty) {

    }
  }

  void _updateAccessToken() {
    defaultParams['X-JWT'] = UserInfoProxy.getInstance().getAccessToken();
    dio.options.headers = defaultParams;
  }

  void seBaseOptionsExtra(Map<String, dynamic> extra) {
    dio.options.extra = extra;
  }

  Future post({@required String url, Map params, CancelToken cancelToken, RequestConfig config}) async {
    return request(method: 'post', url: url,params: params, config: config);
  }

  Future get({@required String url, Map params, CancelToken cancelToken, RequestConfig config}) async {
    return request(method: 'get', url: url, params: params, config: config);
  }

  Future request({@required String method, @required String url, Map<String, dynamic> params, CancelToken cancelToken, RequestConfig config}) async {
//    /// 全局拦截器
//    /// 创建默认的全局拦截器
//    Interceptor dInter = InterceptorsWrapper(onRequest: (options) {
////              print("请求拦截");
//      return options;
//    }, onResponse: (response) {
////              print("响应拦截");
//      return response;
//    }, onError: (err) {
////              print("错误拦截");
//      /// 异常弹窗
//      /// todo : 异常处理（存储）
//      AbnormalToast.showAbnormalToast();
//      return err;
//    });
//
//    /// 可往inters中添加多个拦截器
//    List<Interceptor> inters = [dInter];
//
//    /// 统一添加到拦截器中
//    dio.interceptors.addAll(inters);

    _updateAccessToken();

    Map<String, dynamic> extra = {
      "config": config
    };

    seBaseOptionsExtra(extra);

    _showNoNetToast(config);

    try{
      Response response ;
      if (method == 'get') {
        response = await dio.get(url,queryParameters:params,cancelToken: cancelToken);
      } else if (method == 'post') {
        response = await dio.post(url,data:params, cancelToken: cancelToken);
      }
      if (response.statusCode == HttpStatus.ok) {
//        await WXInterfaceCache.writeDataWithKey(url.split('/').last, response.data);
        return response.data;
      } else {
        return '后端接口出现异常,请检测代码和服务器情况......';
      }
    } catch(error) {
      wxLog("Error:=====>$error");
      return error;
    }
  }

  Future<void> requestWithCallback<T>(
      {Method method = Method.GET,
        @required String url,
        Map params,
        T model,
        RequestConfig config,
        CancelToken cancelToken,
        @required Function success,
        Function(ErrorModel) error}) async {

    if (config !=null) {
      if (config.isMock && !kReleaseMode) {
        await _useMock(config, url: url,model: model,success: success, error: error);
        return;
      } else if (config.isShowToast) {
        _showNoNetToast(config);
      }
    }

    /// 封装extra放入BaseOptions便于拦截器重连
    Map<String, dynamic> extra = {
      "config": config,
      "model": model,
      "success": success,
      "error": error
    };

    seBaseOptionsExtra(extra);

    _updateAccessToken();

    try {

      final response = await dio.request(
          url,
          data: method == Method.POST ? params : {},
          queryParameters: method == Method.GET ? params : {},
          cancelToken: cancelToken ?? _cancelToken,
          options: Options(method: _MethodValues[method])
      );

      if (response != null && response.statusCode == HttpStatus.ok && response.data != null) {
        _resultCallback(
          model: model,
          response: response.data,
          success: success,
          error: error
        );
      } else {
        _errorCallback(error, ErrorModel.create(DioError(type: DioErrorType.RESPONSE,response: response)));
      }
    } on DioError catch(e) {
      _errorCallback(error, ErrorModel.create(e));
    } on Exception catch (e) {
      _errorCallback(error, ErrorModel.create(e));
    }
  }
  /// cancel a request using a cancel token
  void cancelRequests({CancelToken token}) {
    token ?? _cancelToken.cancel("cancelled");
  }

  /// release环境不走mock数据
  Future<void> _useMock<T>(RequestConfig config, {String url, T model, @required Function success, Function(ErrorModel) error}) async {
    String result;
    String mockPath;

    int startTime = config.mockDelay > 0 ? new DateTime.now().millisecondsSinceEpoch : 0;

    /// Use the [url] suffix as mockPath is null
    if (config.mockPath.empty) {
      if (!url.empty) {
        int index = url.lastIndexOf("/");
        if (index >=0 && index < url.length) {
          String subString = url.substring(index+1);
          List<String> subArr = subString.split("."); /// 防止接口是以'.bin'、'.com'等结尾
          if (subArr.length > 1) {
            mockPath = "./assets/mock/${subArr[0]}.json" ;
          } else {
            mockPath = "./assets/mock/$subString.json" ;
          }
        }
      }
    } else {
      mockPath = config.mockPath;
    }

    if (mockPath.isNotEmpty) {
      result = await rootBundle.loadString(mockPath);
    } else {

    }

    wxLog("mock============$url}"
        "...$result");
    int endTime = config.mockDelay > 0 ? new DateTime.now().millisecondsSinceEpoch : 0;

    int difValue = endTime - startTime;

    int delayValue = difValue > config.mockDelay ? 0 : config.mockDelay - difValue;

    try {
      if (result.isNotEmpty) {
        Future.delayed(Duration(milliseconds: delayValue), ()=> {
          _resultCallback(
            model: model,
            response: result,
            success: success,
            error: error
          )
        });
      } else {
        wxLog("cann't get mock result,please check again!");
      }
    } on Exception catch (e) {
      _errorCallback(error, ErrorModel.create(e));
    }
  }

  /// 有结果数据时进行解析回调
  void _resultCallback<T>({@required T model,@required String response,@required Function success, Function(ErrorModel) error}) {
    dynamic result;
    if (model != null && model is BaseModel) {
      /// 只有传了model才会进行自动解析，否则返回json数据
      result = model.fromJson(json.decode(response));

      result.errorId == "0"
          ? _successCallback(success, result)
          : _errorCallback(error, ErrorModel(code: int.tryParse(result.errorId), message: result.errorMsg));
    } else {
      result = json.decode(response);

      result["errorId"] == "0"
          ? _successCallback(success, result)
          : _errorCallback(error, ErrorModel(code: int.tryParse(result["errorId"]), message: result["errorMsg"]));
    }
  }

  void _successCallback(Function success, dynamic result) {
    if (success != null) {
      success(result);
    }
  }

  void _errorCallback(Function(ErrorModel) error, ErrorModel errorModel) {
    if (error != null) {
      error(errorModel);
    }
  }

  void _showNoNetToast(RequestConfig config) {
    if (config != null && config.isShowToast) {
      if(NetworkListen.currentState == NetworkState.none) {

        showToast("no Network");
      }
    }
  }

}